package fr.vince.montecarlomultithread;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

import fr.vince.montecarlo.Action;

/**
 * Monte Carlo representation for multi thread computation
 * @author Vince
 *
 */
public class MonteCarloMultiThread {

	private Action action;
	private Double actualPrice; // S
	private Double strikePrice; // X
	private Double time;// T
	private Double interest;// r
	private Double cost;// b
	private Double volatility;// v
	private Integer steps;// nSteps
	private Integer simulations;// nSimulation

	/**
	 * default constructor
	 * @param action
	 * @param actualPrice
	 * @param strikePrice
	 * @param time
	 * @param interest
	 * @param cost
	 * @param volatility
	 * @param steps
	 * @param simulations
	 */
	public MonteCarloMultiThread(Action action, Double actualPrice,
			Double strikePrice, Double time, Double interest, Double cost,
			Double volatility, Integer steps, Integer simulations) {
		super();
		this.action = action;
		this.actualPrice = actualPrice;
		this.strikePrice = strikePrice;
		this.time = time;
		this.interest = interest;
		this.cost = cost;
		this.volatility = volatility;
		this.steps = steps;
		this.simulations = simulations;
	}

	public Double doMonteCarlo(Action action,
			final double actualPrice, final double strikePrice, double time,
			double interest, double cost, double volatility, final int steps,
			int simulations) {
		long start = System.nanoTime();
		int numberOfCores = Runtime.getRuntime().availableProcessors();
		double blockingCoefficient = 0.1;
		final int poolSize = (int) (numberOfCores / (1 - blockingCoefficient));

		System.out.println("Number of Cores available is " + numberOfCores);
		System.out.println("Pool size is " + poolSize);
		final List<Callable<Double>> simulationsComputation = new ArrayList<Callable<Double>>();

		double dt;

		double sum = 0;
		final double sqrt;
		int z = 0;
		dt = time / steps;
		final double drift = (cost - Math.pow(volatility, 2) / 2) * dt;
		sqrt = volatility * Math.sqrt(dt);
		switch (action) {
		case CALL:
			z = 1;
			break;
		case PUT:
			z = -1;
			break;
		}
		final int z2 = z;

		for (int i = 0; i < simulations; i++) {
			simulationsComputation.add(new Callable<Double>() {
				public Double call() throws Exception {
					double st;
					st = actualPrice;
					for (int j = 0; j < steps; j++) {
						Random rand = new Random();
						st *= Math.exp(drift + sqrt * rand.nextGaussian());
					}
					return Math.max(z2 * (st - strikePrice), 0);
				}
			});
		}

		try {
			ExecutorService executorPool = Executors
					.newFixedThreadPool(poolSize);// create the pool
			List<Future<Double>> stValues = executorPool.invokeAll(
					simulationsComputation, 1000000, TimeUnit.SECONDS);
			for (Future<Double> val : stValues)
				sum += val.get();
		} catch (Exception e) {
			System.out.println("Error while computation with thread : " + e);
			e.printStackTrace();
		}

		System.out.println("Execution time "
				+ ((System.nanoTime() - start) / 1000000000.0) + " s");
		return Math.exp(-interest * time) * (sum / simulations);
	}
	
	/**
	 * do multi threaded computation of monte carlo algorithm
	 * @return
	 */
	public Double doMonteCarlo() {
		long start = System.nanoTime();
		int numberOfCores = Runtime.getRuntime().availableProcessors();
		double blockingCoefficient = 0.1;
		int poolSize = (int) (numberOfCores / (1 - blockingCoefficient));

		System.out.println("Number of Cores available is " + numberOfCores);
		System.out.println("Pool size is " + poolSize);
		final List<Callable<Double>> simulationsComputation = new ArrayList<Callable<Double>>();

		double dt;

		double sum = 0;
		final double sqrt;
		int z = 0;
		dt = time / steps;
		final double drift = (cost - Math.pow(volatility, 2) / 2) * dt;
		sqrt = volatility * Math.sqrt(dt);
		switch (action) {
		case CALL:
			z = 1;
			break;
		case PUT:
			z = -1;
			break;
		}
		final int z2 = z;

		for (int i = 0; i < simulations; i++) {
			simulationsComputation.add(new Callable<Double>() {
				public Double call() throws Exception {
					double st;
					st = actualPrice;
					for (int j = 0; j < steps; j++) {
						Random rand = new Random();
						st *= Math.exp(drift + sqrt * rand.nextGaussian());
					}
					return Math.max(z2 * (st - strikePrice), 0);
				}
			});
		}

		try {
			ExecutorService executorPool = Executors
					.newFixedThreadPool(poolSize);// create the pool
			List<Future<Double>> stValues = executorPool.invokeAll(
					simulationsComputation, 1000000, TimeUnit.SECONDS);
			for (Future<Double> val : stValues)
				sum += val.get();
		} catch (Exception e) {
			System.out.println("Error while computation with thread : " + e);
			e.printStackTrace();
		}

		System.out.println("Execution time "
				+ ((System.nanoTime() - start) / 1000000000.0) + " s");
		return Math.exp(-interest * time) * (sum / simulations);
	}
}
